/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-08-24     xu     first version
 */
#include "drv_spi.h"
#include "drivers/spi.h"
#include "hal_spi.h"
#include "hal_rcc.h"

#define DBG_TAG "drv.spi"

// #define DEBUG

#ifdef DEBUG
#define DBG_LVL DBG_LOG
#else
#define DBG_LVL DBG_INFO
#endif
#include <rtdbg.h>

#ifdef RT_USING_SPI

#define SPI_TIME_OUT (3000)

struct mm32_spi_cs
{
    GPIO_Type *gpio;
    uint16_t pin;
};

struct mm32_spi_config
{
    SPI_Type *spi_handle;
    const char *bus_name;
    rt_uint32_t clock_freq; // 主模式才会用到，将传给 SPI_Master_Init_Type.ClockFreqHz
};

struct mm32_spi
{
    SPI_Type *spi_handle;
    struct mm32_spi_config *soc_cfg;            // SPI总线配置,用于给底层接口传参数
    const struct rt_spi_configuration *drv_cfg; // RT-Thread SPI驱动框架配置,由上层传下来
    struct rt_spi_bus spi_bus;
};

enum
{
    SPI1_INDEX,
    SPI2_INDEX,
    SPI3_INDEX,
    SPI_MAX
};

static struct mm32_spi_config spi_config[] = {
    {SPI1, "spi1", CLOCK_APB2_FREQ},
    {SPI2, "spi2", CLOCK_APB1_FREQ},
    {SPI3, "spi3", CLOCK_APB1_FREQ},
};

static struct mm32_spi spi_bus_obj[SPI_MAX] = {0};

static rt_err_t mm32_spi_init(struct mm32_spi *spi_bus, struct mm32_spi_cs *cs_pin);

static rt_err_t mm32_spi_configure(struct rt_spi_device *device,
                                   struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    struct mm32_spi *spi_bus = rt_container_of(device->bus, struct mm32_spi, spi_bus);
    spi_bus->drv_cfg = configuration;

    struct mm32_spi_cs *cs_pin = (struct mm32_spi_cs *)device->parent.user_data;
    return mm32_spi_init(spi_bus, cs_pin);
}

void mm32_gpio_pin_init(GPIO_Type *gpiox, uint16_t pin, uint8_t afn, GPIO_PinMode_Type mode, GPIO_Speed_Type speed)
{
    GPIO_Init_Type gpio_init;
    gpio_init.Pins = pin;
    gpio_init.PinMode = mode;
    gpio_init.Speed = speed;
    GPIO_Init(gpiox, &gpio_init);
    GPIO_PinAFConf(gpiox, pin, afn);
}

// 这里配置SPI实际引脚和外设时钟，可能需要根据实际情况修改
static void mm32_spi_pin_init(SPI_Type *spi_handle, rt_bool_t master_mode)
{
    if (spi_handle == SPI3)
    {
        // SPI3, enable and reset clock
        RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_SPI3, true);
        RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_SPI3);

        // GPIOC. for PC10, PC11, PC12
        RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOC, true);
        RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOC);

        // PC12 GPIO_AF_6: SPI3_MOSI
        mm32_gpio_pin_init(GPIOC, GPIO_PIN_12, GPIO_AF_6, GPIO_PinMode_AF_PushPull, GPIO_Speed_50MHz);

        // PC11 GPIO_AF_6: SPI3_MISO
        mm32_gpio_pin_init(GPIOC, GPIO_PIN_11, GPIO_AF_6, GPIO_PinMode_In_Floating, GPIO_Speed_50MHz);

        if (master_mode)
        {
            // PC10 GPIO_AF_6: SPI3_SCK
            mm32_gpio_pin_init(GPIOC, GPIO_PIN_10, GPIO_AF_6, GPIO_PinMode_AF_PushPull, GPIO_Speed_50MHz);
        }
    }
    else
    {
        LOG_E("%s: invalid spi_handle, no configurations for SPI1 and SPI2!", __func__);
    }
}

// typedef enum
// {
//     SPI_PolPha_Alt0 = 0u, /*!< CPOL = 0, CPHA = 1, Clock line is low when idle, Data valid when at falling edge */
//     SPI_PolPha_Alt1 = 1u, /*!< CPOL = 0, CPHA = 0, Clock line is low when idle, Data valid when at rising edge */
//     SPI_PolPha_Alt2 = 2u, /*!< CPOL = 1, CPHA = 1, Clock line is high when idle, Data valid when at rising edge */
//     SPI_PolPha_Alt3 = 3u, /*!< CPOL = 1, CPHA = 0, Clock line is high when idle, Data valid when at falling edge */
// }   SPI_PolPha_Type;
static SPI_PolPha_Type mm32_polpha_from_mode(rt_uint8_t mode)
{
    rt_bool_t cpol = (mode & RT_SPI_CPOL) ? RT_TRUE : RT_FALSE;
    rt_bool_t cpha = (mode & RT_SPI_CPHA) ? RT_TRUE : RT_FALSE;

    if (cpol && cpha)
    {
        return SPI_PolPha_Alt2; // CPOL = 1, CPHA = 1
    }

    if (cpol)
    {
        return SPI_PolPha_Alt3; // CPOL = 1, CPHA = 0
    }

    if (cpha)
    {
        return SPI_PolPha_Alt0; // CPOL = 0, CPHA = 1
    }

    return SPI_PolPha_Alt1; // CPOL = 0, CPHA = 0
}

static rt_err_t mm32_spi_init(struct mm32_spi *spi_bus, struct mm32_spi_cs *cs_pin)
{
    struct mm32_spi_config *soc_cfg = spi_bus->soc_cfg;
    const struct rt_spi_configuration *drv_cfg = spi_bus->drv_cfg;
    RT_ASSERT(spi_bus != RT_NULL);
    RT_ASSERT((drv_cfg->data_width % 8) == 0);

    // check is master mode or not
    rt_bool_t master_mode = (drv_cfg->mode & RT_SPI_SLAVE) ? RT_FALSE : RT_TRUE;
    LOG_D("master_mode=%d, peed=%d, data_width=%d", master_mode, drv_cfg->max_hz, drv_cfg->data_width);

    // set SPI bus pins as SPI function
    mm32_spi_pin_init(soc_cfg->spi_handle, master_mode);

    // prepare SPI config, setup SPI master/slave
    if (master_mode)
    {
        SPI_Master_Init_Type master_cfg;
        master_cfg.ClockFreqHz = soc_cfg->clock_freq;
        master_cfg.BaudRate = drv_cfg->max_hz;
        master_cfg.PolPha = mm32_polpha_from_mode(drv_cfg->mode);
        master_cfg.DataWidth = drv_cfg->data_width;
        master_cfg.XferMode = SPI_XferMode_TxRx;
        master_cfg.AutoCS = cs_pin ? false : true;
        master_cfg.LSB = (drv_cfg->mode & RT_SPI_MSB) ? false : true;
        SPI_InitMaster(soc_cfg->spi_handle, &master_cfg);
    }
    else
    {
        SPI_Slave_Init_Type slave_cfg;
        slave_cfg.PolPha = mm32_polpha_from_mode(drv_cfg->mode);
        slave_cfg.DataWidth = drv_cfg->data_width;
        slave_cfg.XferMode = SPI_XferMode_TxRx;
        slave_cfg.AutoCS = false;
        slave_cfg.LSB = (drv_cfg->mode & RT_SPI_MSB) ? false : true;
        SPI_InitSlave(soc_cfg->spi_handle, &slave_cfg);
    }

    // eanble SPI master/slave
    SPI_Enable(soc_cfg->spi_handle, true);
    LOG_I("%s done!", __func__);
    return RT_EOK;
}

static rt_err_t _spi_xfer(SPI_Type *spi, const rt_uint8_t *tx_buff, rt_uint8_t *rx_buff, rt_uint32_t length, rt_uint32_t timeout);

static rt_uint32_t mm32_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    rt_size_t xfer_len;
    rt_uint8_t *recv_buf;
    rt_uint8_t *send_buf;
    SPI_Type *spi = RT_NULL;
    rt_err_t stat = RT_EOK;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(message != RT_NULL);
    RT_ASSERT(device->parent.user_data != RT_NULL);

    struct mm32_spi *spi_bus = rt_container_of(device->bus, struct mm32_spi, spi_bus);
    struct mm32_spi_cs *cs = (struct mm32_spi_cs *)device->parent.user_data;

    if (message->cs_take && !(device->config.mode & RT_SPI_NO_CS))
    {
        LOG_D("%s: cs_take, %d", __func__, device->config.mode);
        if (device->config.mode & RT_SPI_CS_HIGH)
        {
            GPIO_SetBits(cs->gpio, cs->pin);
        }
        else
        {
            GPIO_ClearBits(cs->gpio, cs->pin);
        }
    }

    spi = spi_bus->spi_handle;
    xfer_len = message->length;
    recv_buf = (rt_uint8_t *)message->recv_buf;
    send_buf = (rt_uint8_t *)message->send_buf;
    LOG_D("%s: xfer_len=%d, %4s %4s", __func__, xfer_len, (send_buf ? "send" : ""), (recv_buf ? "recv" : ""));

    if (message->send_buf && message->recv_buf)
    {
        // 同时发送、接收，上层需要保证 send_buf、recv_buf 可访问长度 不小于 xfer_len
        stat = _spi_xfer(spi, send_buf, recv_buf, xfer_len, SPI_TIME_OUT);
    }
    else if (message->send_buf)
    {
        // 只发送
        stat = _spi_xfer(spi, send_buf, RT_NULL, xfer_len, SPI_TIME_OUT);
    }
    else if (message->recv_buf)
    {
        rt_memset(recv_buf, 0xff, xfer_len);
        stat = _spi_xfer(spi,
                         recv_buf, // 生成足够的时钟信号
                         recv_buf, xfer_len, SPI_TIME_OUT);
    }
    else
    {
        LOG_E("%s: both send_buf and recv_buf is null!", __FUNCTION__);
        stat = RT_EIO;
    }

    if (message->cs_release && !(device->config.mode & RT_SPI_NO_CS))
    {
        LOG_D("%s: cs_rel, %d", __func__, device->config.mode);
        if (device->config.mode & RT_SPI_CS_HIGH)
        {
            GPIO_ClearBits(cs->gpio, cs->pin);
        }
        else
        {
            GPIO_SetBits(cs->gpio, cs->pin);
        }
    }

    if (stat != RT_EOK)
    {
        xfer_len = 0;
    }

    return xfer_len;
}

static rt_err_t _spi_xfer(SPI_Type *spi,
                          const rt_uint8_t *tx_buff,
                          rt_uint8_t *rx_buff,
                          rt_uint32_t length,
                          rt_uint32_t timeout)
{
    rt_uint32_t start = rt_tick_get();
    rt_uint8_t dat = 0;

    if ((tx_buff == RT_NULL) || (length == 0))
    {
        return RT_EIO;
    }

    while (length--)
    {
        // wait for tx not full
        while (SPI_GetStatus(spi) & SPI_STATUS_TX_FULL)
        {
            if ((rt_tick_get() - start) > timeout)
            {
                return RT_ETIMEOUT;
            }
        }
        // send a byte
        SPI_PutData(spi, *tx_buff++);

        // wait for rx done
        while (!(SPI_GetStatus(spi) & SPI_STATUS_RX_DONE))
        {
            if ((rt_tick_get() - start) > timeout)
            {
                return RT_ETIMEOUT;
            }
        }

        // recv a byte
        dat = SPI_GetData(spi);
        if (rx_buff)
        {
            *rx_buff++ = dat;
        }
    }
    return RT_EOK;
}

static const struct rt_spi_ops mm32_spi_ops = {
    .configure = mm32_spi_configure,
    .xfer = mm32_spi_xfer,
};

static const struct
{
    GPIO_Type *gpio;
    rt_uint32_t rcc;
} gpio_rcc_table[] = {
    {GPIOA, RCC_AHB1_PERIPH_GPIOA},
    {GPIOB, RCC_AHB1_PERIPH_GPIOB},
    {GPIOC, RCC_AHB1_PERIPH_GPIOC},
    {GPIOD, RCC_AHB1_PERIPH_GPIOD},
    {GPIOE, RCC_AHB1_PERIPH_GPIOE},
    {GPIOF, RCC_AHB1_PERIPH_GPIOF},
    {GPIOG, RCC_AHB1_PERIPH_GPIOG},
    {GPIOH, RCC_AHB1_PERIPH_GPIOH},
    {GPIOI, RCC_AHB1_PERIPH_GPIOI},
};

rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_Type *cs_gpio, uint16_t cs_pin)
{
    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);
    RT_ASSERT(cs_gpio != RT_NULL);

    rt_err_t result = RT_EOK;
    struct rt_spi_device *spi_device = RT_NULL;
    struct mm32_spi_cs *spi_cs_pin = RT_NULL;

    // enable and reset cs GPIO clock
    for (rt_size_t i = 0; i < sizeof(gpio_rcc_table) / sizeof(gpio_rcc_table[0]); i++)
    {
        if (gpio_rcc_table[i].gpio == cs_gpio)
        {
            RCC_EnableAHB1Periphs(gpio_rcc_table[i].rcc, true);
            RCC_ResetAHB1Periphs(gpio_rcc_table[i].rcc);
            break;
        }
    }

    // prepare and init GPIO
    GPIO_Init_Type gpio_init;
    gpio_init.Pins = cs_pin;
    gpio_init.Speed = GPIO_Speed_50MHz;
    gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
    GPIO_Init(cs_gpio, &gpio_init);

    // create mm32_spi_cs object
    spi_cs_pin = (struct mm32_spi_cs *)rt_calloc(sizeof(struct mm32_spi_cs), 1);
    spi_cs_pin->gpio = cs_gpio;
    spi_cs_pin->pin = cs_pin;

    // create spi_device object
    spi_device = (struct rt_spi_device *)rt_calloc(sizeof(struct rt_spi_device), 1);
    RT_ASSERT(spi_device != RT_NULL);

    // attach spi_device to spi bus
    result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)spi_cs_pin);
    if (result != RT_EOK)
    {
        LOG_E("%s attach to bus %s faild, %d\n", device_name, bus_name, result);
        return result;
    }

    LOG_D("%s attach to bus %s done", device_name, bus_name);
    return RT_EOK;
}

static int rt_hw_spi_bus_init(void)
{
    rt_err_t result = RT_EOK;

    for (rt_size_t i = 0; i < SPI_MAX; i++)
    {
        spi_bus_obj[i].soc_cfg = &spi_config[i];
        spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];
        spi_bus_obj[i].spi_handle = spi_config[i].spi_handle;

        result = rt_spi_bus_register(&spi_bus_obj[i].spi_bus, spi_config[i].bus_name, &mm32_spi_ops);
        RT_ASSERT(result == RT_EOK);

        LOG_D("SPI bus device %s register done!", spi_config[i].bus_name);
    }
    return result;
}
INIT_BOARD_EXPORT(rt_hw_spi_bus_init);

#endif
